﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using Fine.Collections.Intf;
using Fine.Collections.Views;
using JetBrains.Annotations;
using Stereotypes.Annotations;



namespace Fine.Collections.Const
{

    /// <summary>
    /// Constant sorted map.
    /// </summary>
    /// <typeparam name="K">type of an entry key.</typeparam>
    /// <typeparam name="V">type of an entry value.</typeparam>
    [DebuggerDisplay("Count = {Count}")]
    [DebuggerTypeProxy(typeof(ConstSortMap<,>.DebugView))]
    [ValueObject]
    public sealed class ConstSortMap<K,V> : RMap<K,V>, Constant
        where K: IComparable<K>
    {
        /// <summary>
        /// Set of key-value pairs.
        /// </summary>
        private readonly ConstSortSet<ComparableMapEntry<K,V>> entries;

        /// <summary>
        /// Keys comparer.
        /// </summary>
        internal static readonly IComparer<K> keyComparer =
            Comparer<K>.Default;

        /// <summary>
        /// Values comparer (for equality only).
        /// </summary>
        internal static readonly IEqualityComparer<V> valueComparer =
            EqualityComparer<V>.Default;

        /// <summary>
        /// Keys view.
        /// </summary>
        private readonly RRandomSet<K> keys;

        /// <summary>
        /// Values view.
        /// </summary>
        private readonly RRandomList<V> values;


        internal ConstSortMap(int firstIndex, ComparableMapEntry<K,V>[] entries) 
            : this (firstIndex, entries, entries.Length, false)
        {}


        internal ConstSortMap(int firstIndex, ComparableMapEntry<K,V>[] entries, int count, bool takeOwnership, bool resort = true, bool removeDuplicates = true)
        {
            this.entries = new ConstSortSet<ComparableMapEntry<K,V>>(firstIndex, entries, count, 
                                                                     resort: resort, removeDuplicates: removeDuplicates, takeOwnership: takeOwnership);
            this.keys = new KeysView(this.entries);
            this.values = new RRandomListAdaptor<ConstSortSet<ComparableMapEntry<K,V>>,ComparableMapEntry<K,V>,V>(this.entries, e => e.Value);
        }


        /// <summary>
        /// Keys as a random-accessible ordered set.
        /// Maintains the native order of keys.
        /// </summary>
        public RRandomSet<K> Keys
        {
            get { return keys; }
        }


        RSet<K> RMap<K,V>.Keys
        {
            get { return keys; }
        }


        /// <summary>
        /// Values as a random-accessible list.
        /// The order of values is the same as the oreder of their keys.
        /// </summary>
        public RRandomList<V> Values
        {
            get { return values; }
        }


        RCollection<V> RMap<K,V>.Values
        {
            get { return values; }
        }


        /// <summary>
        /// Entries as random-accessible list.
        /// The order of entries is the same as the oreder of their keys.
        /// </summary>
        public RRandomList<RMapEntry<K, V>> Entries
        {
            get { return entries; }
        }


        RCollection<RMapEntry<K,V>> RMap<K,V>.Entries
        {
            get { return entries; }
        }


        public bool IsEmpty
        {
            get { return entries.IsEmpty; }
        }

        public bool IsNotEmpty
        {
            get { return entries.IsNotEmpty; }
        }

        public int FirstIndex
        {
            get { return entries.FirstIndex; }
        }

        public int LastIndex
        {
            get { return entries.LastIndex; }
        }

        public int Count
        {
            get { return entries.Count; }
        }


        public SearchResult<V> Find(K key)
        {
            int zIndex = ZeroBasedIndexOf(entries, key);
            return (zIndex >= 0) 
                    ? SearchResults<V>.Found(entries.arr[zIndex].Value) 
                    : SearchResults<V>.NotFound();
        }


        public V this[K key]
        {
            get
            {
                int zIndex = ZeroBasedIndexOf(entries, key);
                return zIndex >= 0 ? entries.arr[zIndex].Value : default(V);
            }
        }


        public bool ContainsKey(K key)
        {
            int index = IndexOf(key);
            return index > 0;
        }


        /// <summary>
        /// Looks for the map entry with the specified key, and return its index.
        /// </summary>
        /// <param name="key">key for looking for.</param>
        /// <returns>index of the found entry (started with <see cref="ConstArray{T}.FirstIndex"/>),
        ///          or <see cref="int.MinValue"/> if not found.</returns>
        public int IndexOf(K key)
        {
            int zIndex = ZeroBasedIndexOf(entries, key);
            return zIndex >= 0 ? entries.firstIndex + zIndex : int.MinValue;
        }


        private static int ZeroBasedIndexOf([NotNull] ConstSortSet<ComparableMapEntry<K,V>> entries, K key) 
        {
            if (entries.count == 0)
                return int.MinValue;

            var fakeEntry = new MapEntry<K, V>(key, default(V));
            return Array.BinarySearch(entries.arr, 0, entries.count, fakeEntry, EntriesComparer);
        }



        #region MAP ENTRIES COMPARATOR

        internal class MapEntriesComparer : IComparer<MapEntry<K, V>>
        {
            public int Compare(MapEntry<K,V> x, MapEntry<K,V> y)
            {
                if (x == y) return 0;
                if (x == null) return -1;
                if (y == null) return +1;
                return keyComparer.Compare(x.Key, y.Key);
            }
        }

        internal static readonly MapEntriesComparer EntriesComparer = new MapEntriesComparer();

        #endregion



        #region KEYS VIEW

        private sealed class KeysView : RRandomListAdaptor<ConstSortSet<ComparableMapEntry<K,V>>,ComparableMapEntry<K,V>,K>,
                                        RRandomSet<K>
        {
            internal KeysView(ConstSortSet<ComparableMapEntry<K,V>> entries)
                : base(entries, e => e.Key)
            {}


            public bool Contains(K item)
            {
                int zIndex = ZeroBasedIndexOf(source, item);
                return zIndex >= 0;
            }


            public void Find(K item, out bool found, out int position)
            {
                var fakeEntry = new ComparableMapEntry<K,V>(item, default(V));
                source.Find(fakeEntry, out found, out position);
            }


            public int IndexOf(K item)
            {
                var fakeEntry = new ComparableMapEntry<K,V>(item, default(V));
                return source.IndexOf(fakeEntry);
            }


            public new ROrderIterator<K> TakeIterator()
            {
                return new KeysIterator(source.TakeIterator());
            }
        }


        private sealed class KeysIterator : RIteratorAdaptor<ComparableMapEntry<K,V>,K>,
                                            ROrderIterator<K>
        {
            public KeysIterator([NotNull] ROrderIterator<ComparableMapEntry<K,V>> iterator)
                : base(iterator, e => e.Key) 
            {}


            public JumpResult Jump(K item)
            {
                var fakeEntry = new ComparableMapEntry<K,V>(item, default(V));
                return ((ROrderIterator<ComparableMapEntry<K,V>>) it).Jump(fakeEntry);
            }
        }

        #endregion



        #region DEBUG HELPER

        /// <summary>
        /// Debug helper.
        /// </summary>
        internal class DebugView
        {
            private readonly ConstSortSet<ComparableMapEntry<K,V>> entries;


            public DebugView(ConstSortMap<K,V> map)
            {
                this.entries = map.entries;
            }


            [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
            public Array Items
            {
                get
                {
                    return DebugViewUtils.MakeArray(entries.arr, entries.firstIndex, entries.count); 
                }
            }
        }

        #endregion
    }


}